﻿using System;
using System.Collections.Generic;
using System.Text;
using AdaptiveAgents.Agents;
using AdaptiveAgents.Games;
using AdaptiveAgents.Distributions;
using AdaptiveAgents.Loggers;
using System.Data;
using AdaptiveAgents.Results;

namespace AdaptiveAgents.Experiments
{
    /// <summary>
    /// The basic experiment to find out what is the most efficient epsilon to use.
    /// </summary>
    class NewAdaptiveExperiment : Experiment
    {
        public NewAdaptiveExperiment() : base(50, 3, 1) { }
        private ExperimentResults results;

        private DataTable dtNormal;//A DataTable for a normal run

        /// <summary>
        /// A function to write the XML file as a CSV file ready for opening in excel
        /// </summary>
        /// <param name="fileName">Name of the file</param>
        /// <param name="dt">The data Table to print</param>
        private void WriteToCSV(String fileName, DataTable dt)
        {
            //set an output writer
            System.IO.TextWriter writer = new System.IO.StreamWriter(fileName);

            //print columns name
            for (int i = 0; i < dt.Columns.Count; ++i)
                writer.Write(dt.Columns[i].ToString() + ",");

            writer.WriteLine();

            //for each row...
            for (int i = 0; i < dt.Rows.Count; ++i)
            {
                DataRow row = dt.Rows[i];
                //for each cell in the row...
                for (int j = 0; j < dt.Columns.Count; ++j)
                    writer.Write(row[j].ToString() + ",");
                writer.WriteLine();
            }

            writer.Close();
        }

        /// <summary>
        /// Run the main experiment
        /// </summary>       
        public override void runExperiment()
        {
            results = new ExperimentResults();


            int GAMES = 100; //number of games to run
            int ROUNDS = 400; //number of round each game
            DataRow row1; //a row for each type of agent

            //Set the competence level, the higher it is - the better the player is.
            const double competence0 = 0.2;
            const double competence1 = 0.5;
            const double competence2 = 0.8;

            double utility = 0;

            dtNormal = new DataTable("Normal_Data_Table");
            DataColumn cln = new DataColumn("GameNum", typeof(double));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("Competence0", typeof(double));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("Competence1", typeof(double));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("Competence2", typeof(double));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("eps0", typeof(double));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("eps1", typeof(double));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("eps2", typeof(double));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("avgUtilityOfTheGame", typeof(double));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("Player0NumOfTurnsPlayed", typeof(int));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("Player1NumOfTurnsPlayed", typeof(int));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("Player2NumOfTurnsPlayed", typeof(int));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("Player0AccumUtility", typeof(double));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("Player1AccumUtility", typeof(double));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("Player2AccumUtility", typeof(double));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("Player0AvgUtility", typeof(double));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("Player1AvgUtility", typeof(double));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("Player2AvgUtility", typeof(double));
            dtNormal.Columns.Add(cln);


            //add rows for each round
            for (int i = 0; i < ROUNDS; ++i)
            {
                cln = new DataColumn(i.ToString(), typeof(double));
                dtNormal.Columns.Add("utility " + cln);
            }

            //run games
            for (int i = 0; i < GAMES; ++i)
            {
                //write to console to see progress
                if (i % 100 == 0)
                    System.Console.WriteLine(i);

                //set new rows for the game
                row1 = dtNormal.NewRow();

                //enter game number
                row1["GameNum"] = i;

                //save the epsilon value in the DataRows
                row1["eps0"] = 0.5;
                row1["eps1"] = 0.3;
                row1["eps2"] = 0.8;

                //save the competence value in the DataRows
                row1["competence0"] = competence0;
                row1["competence1"] = competence1;
                row1["competence2"] = competence2;

                //Create a new environment for the experiment
                Environment environment = new Environment();

                //Create a list of 3 agents
                List<Agent> agents = generatePlayers(3, environment, 1, 0.5, 0.3, 0.8, AdaptiveAgents.logger);

                //Add agents to the environment
                for (int j = 0; j < NumAgents; j++)
                {
                    Agent agent = agents[j];
                    environment.addAgent(agent);
                }

                //Create a new normal game
                //NormalGame game = new NormalGame(environment, 10);
                NormalGameToCSV game = (NormalGameToCSV)GameFactory.create(Games.GameType.NormalToCsv, environment, ROUNDS);

                //Set the competence level for each agent
                environment.agents[0].Competence = new Blocks(competence0);
                environment.agents[1].Competence = new Blocks(competence1);
                environment.agents[2].Competence = new Blocks(competence2);

                //enter to the game the row it needs to write the game data to
                game.Dr1 = row1;

                //Start the game
                utility = game.start(AdaptiveAgents.logger, out results);

                row1["avgUtilityOfTheGame"] = utility;
                row1["Player0NumOfTurnsPlayed"] = results.numberOfTurnsPlayer0Played;
                row1["Player1NumOfTurnsPlayed"] = results.numberOfTurnsPlayer1Played;
                row1["Player2NumOfTurnsPlayed"] = results.numberOfTurnsPlayer2Played;

                row1["Player0AccumUtility"] = results.player0AccumilatingProfit;
                row1["Player1AccumUtility"] = results.player1AccumilatingProfit;
                row1["Player2AccumUtility"] = results.player2AccumilatingProfit;

                row1["Player0AvgUtility"] = results.player0AccumilatingProfit / results.numberOfTurnsPlayer0Played;
                row1["Player1AvgUtility"] = results.player1AccumilatingProfit / results.numberOfTurnsPlayer1Played;
                row1["Player2AvgUtility"] = results.player2AccumilatingProfit / results.numberOfTurnsPlayer2Played;

                dtNormal.Rows.Add(row1);

                //Print to screen current utility if needed (cancel to increase speed)
                //System.Console.WriteLine(utility);
            }
            //create file names and write to the files
            String stamp = DateTime.Now.ToString("yyyy.MM.dd-HH.mm.ss");
            //dtNormal.WriteXml("FindBestEpsilonExperiment_" + stamp + ".csv");

            WriteToCSV("FindBestEpsilonExperiment_" + stamp + ".csv", dtNormal);
        }

        protected List<Agent> generatePlayers(int numOfAgents, Environment env, int numAdaptive, double adaptiveEpsilon, double normalEpsilon1, double normalEpsilon2, Logger logger)
        {
            List<Agent> retList = new List<Agent>(numOfAgents);

            AgentDirector director = new AgentDirector();

            //create adaptive agent
            director.Builder = new Agents.NewAdaptiveBuilder();
            director.construct(0, env, adaptiveEpsilon, logger);
            retList.Add(director.getAgent());


            //create normal (epsilon greedy) agent1
            director.Builder = new Agents.NormalBuilder();
            director.construct(1, env, normalEpsilon1, logger);
            retList.Add(director.getAgent());

            //create normal (epsilon greedy) agent2
            director.Builder = new Agents.NormalBuilder();
            director.construct(2, env, normalEpsilon2, logger);
            retList.Add(director.getAgent());

            return retList;
        }
    }
}
